package es.unex.meigas.ww.core;

import java.awt.Color;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.geotools.data.DataStore;
import org.geotools.data.FeatureSource;
import org.geotools.data.FeatureStore;
import org.geotools.data.FileDataStoreFactorySpi;
import org.geotools.data.memory.MemoryDataStore;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.AttributeTypeBuilder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureCollections;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.LineSymbolizer;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.PolygonSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.SLD;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.geotools.styling.Symbolizer;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.GeometryType;
import org.opengis.filter.FilterFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;

import es.unex.meigas.core.Meigas;
import es.unex.meigas.dataObjects.AbstractVectorLayer;
import es.unex.meigas.dataObjects.IFeatureIterator;
import es.unex.meigas.dataObjects.IVectorLayer;
import es.unex.meigas.ww.core.PostProcessStrategy;
import es.unex.meigas.ww.core.WWFeatureIterator;
import es.unex.meigas.ww.gui.WWGUIFactory;
import gov.nasa.worldwind.WorldWindow;
import gw.layers.SimpleFeatureLayer;
import gw.util.ProjectionUtils;

/**
 * This class wraps an WW vector layer, so it can be used by
 * SEXTANTE. It will also be used by SEXTANTE algorithms to create
 * new vector layers from algorithms, so they are based on the WW
 * data model
 * @author volaya
 *
 */
public class WWVectorLayer extends AbstractVectorLayer {

	private String m_sFilename;
	private String m_sName;
	private int m_iCount;
	private CoordinateReferenceSystem m_CRS;
	private PostProcessStrategy m_PostProcessStrategy;

	public void create(SimpleFeatureLayer layer) {

		try {
			FeatureSource<SimpleFeatureType, SimpleFeature> featureSource =
				(FeatureSource<SimpleFeatureType, SimpleFeature>) layer.getFeatureSource();
			m_BaseDataObject = layer;
			m_iCount = featureSource.getFeatures().size();
			m_CRS = featureSource.getSchema().getCoordinateReferenceSystem();
			m_sName = layer.getName();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	public void create(String sName, int iShapeType, Class<?>[] fields,
			String[] sFields, String filename, Object crs) {

		try {

			m_sFilename = filename;
			m_sName = sName;

			if (!(crs instanceof CoordinateReferenceSystem)) {
				crs = ProjectionUtils.getDefaultCRS();
			}

			SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
			builder.setName(sName);

			AttributeTypeBuilder attBuilder = new AttributeTypeBuilder();
			builder.add(toGeometryAttribute(iShapeType,
					(CoordinateReferenceSystem) crs,attBuilder));
			builder.setDefaultGeometry("geom");
			for (int i = 0; i < sFields.length; i++) {
				AttributeType type = attBuilder.binding(fields[i]).buildType();
				AttributeDescriptor descriptor = attBuilder.buildDescriptor(sFields[i], type);
				builder.add(descriptor);
			}
			DataStore mds = new MemoryDataStore();

			mds.createSchema(builder.buildFeatureType());

			m_BaseDataObject = mds.getFeatureSource(mds.getTypeNames()[0]);

		}
		catch(Exception e){}

	}

    static final StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory(null);
    static final FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory(null);

    private static Style createPointStyle() {
        Style style;
        PointSymbolizer symbolizer = styleFactory.createPointSymbolizer();
        symbolizer.getGraphic().setSize(filterFactory.literal(4));
        Rule rule = styleFactory.createRule();
        rule.setSymbolizers(new Symbolizer[]{symbolizer});
        FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle();
        fts.setRules(new Rule[]{rule});
        style = styleFactory.createStyle();
        style.addFeatureTypeStyle(fts);
        return style;
    }

    private static Style createLineStyle() {
        Style style;
        LineSymbolizer symbolizer = styleFactory.createLineSymbolizer();
        SLD.setLineColour(symbolizer, Color.BLUE);
        symbolizer.getStroke().setWidth(filterFactory.literal(1));
        symbolizer.getStroke().setColor(filterFactory.literal(getRandomColorHex()));

        Rule rule = styleFactory.createRule();
        rule.setSymbolizers(new Symbolizer[]{symbolizer});
        FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle();
        fts.setRules(new Rule[]{rule});
        style = styleFactory.createStyle();
        style.addFeatureTypeStyle(fts);
        return style;
    }

    private static Style createPolygonStyle() {
        Style style;
        PolygonSymbolizer symbolizer = styleFactory.createPolygonSymbolizer();

        Fill fill = styleFactory.createFill(
                filterFactory.literal(getRandomColorHex()),
                filterFactory.literal(1.0));

        symbolizer.setFill(fill);
        Rule rule = styleFactory.createRule();
        rule.setSymbolizers(new Symbolizer[]{symbolizer});
        FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle();
        fts.setRules(new Rule[]{rule});
        style = styleFactory.createStyle();
        style.addFeatureTypeStyle(fts);
        return style;
    }

    private static String getRandomColorHex() {
        Random r = new Random();
        Color randomColor = new Color(r.nextInt(255), r.nextInt(255), r.nextInt(255), 0);
        return "#" + Integer.toHexString(randomColor.getRGB());
    }

	private GeometryDescriptor toGeometryAttribute(int shapeType,
			CoordinateReferenceSystem crs, AttributeTypeBuilder builder) {

		Class<?> s[] = { Point.class, MultiLineString.class, MultiPolygon.class };
		GeometryType buildGeometryType = builder.crs(crs).binding(s[shapeType]).buildGeometryType();
		return builder.buildDescriptor("geom", buildGeometryType);
	}

	public void open() {
	}

	public void close() {
		if (this.getFeatureSource() != null) {
			this.getFeatureSource().getDataStore().dispose();
		}
	}

	public void addFeature(Geometry g, Object[] values) {

		if (getFeatureSource() instanceof FeatureStore) {
			Geometry geom;
			GeometryFactory gf = new GeometryFactory();
			if (g instanceof Polygon) {
				geom = gf.createMultiPolygon(new Polygon[] { (Polygon) g });
			} else if (g instanceof LineString) {
				geom = gf.createMultiLineString(new LineString[] { (LineString) g });
			} else {
				geom = g;
			}

			try {
				FeatureStore<SimpleFeatureType, SimpleFeature> store = ((FeatureStore<SimpleFeatureType, SimpleFeature>) getFeatureSource());
				List<Object> attributes = new ArrayList<Object>();
				attributes.add(geom);
				attributes.addAll(Arrays.asList(values));
				SimpleFeatureType ft = store.getSchema();
				FeatureCollection<SimpleFeatureType, SimpleFeature> collection = FeatureCollections
						.newCollection();

				SimpleFeature feature = SimpleFeatureBuilder.build(ft, attributes, SimpleFeatureBuilder.createDefaultFeatureId());
				collection.add(feature);
				store.addFeatures(collection);
				m_iCount++;
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

	public IFeatureIterator iterator() {

		if (m_BaseDataObject != null) {
			try {
				FeatureCollection<SimpleFeatureType, SimpleFeature> features = getFeatureSource().getFeatures();
				return new WWFeatureIterator(features);
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		} else {
			return null;
		}

	}

	public String getFieldName(int i) {

		if (m_BaseDataObject != null) {
			try {
				SimpleFeatureType ft = getFeatureSource().getSchema();
				AttributeType at = ft.getType(i + 1);
				return at.getName().getLocalPart();
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}

		return null;

	}

	public Class<?> getFieldType(int i) {

		if (m_BaseDataObject != null) {
			try {
				SimpleFeatureType ft = getFeatureSource().getSchema();
				AttributeType at = ft.getType(i + 1);
				return at.getBinding();
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}

		return null;

	}

	public int getFieldCount() {

		if (m_BaseDataObject != null) {
			try {
				SimpleFeatureType ft = getFeatureSource().getSchema();
				return ft.getAttributeCount() - 1;
			} catch (Exception e) {
				e.printStackTrace();
				return 0;
			}
		}

		return 0;

	}

	public int getShapesCount() {

		return m_iCount;

	}

	public int getShapeType() {

		if (m_BaseDataObject != null) {
			try {

				SimpleFeatureType ft = getFeatureSource().getSchema();
				Class<?> type = ft.getGeometryDescriptor().getType().getBinding();
				if (type.isAssignableFrom(Polygon.class)
						|| type.isAssignableFrom(MultiPolygon.class)) {
					return IVectorLayer.SHAPE_TYPE_POLYGON;
				} else if (type.isAssignableFrom(LineString.class)
						|| type.isAssignableFrom(MultiLineString.class)) {
					return IVectorLayer.SHAPE_TYPE_LINE;
				} else {
					return IVectorLayer.SHAPE_TYPE_POINT;
				}
			} catch (Exception e) {
				e.printStackTrace();
				return IVectorLayer.SHAPE_TYPE_POLYGON;
			}
		}

		return IVectorLayer.SHAPE_TYPE_POLYGON;

	}

	public String getName() {

		return m_sName;

	}

	public void postProcess() {

		saveShapefile();

		Style s;
		int iShapeType = this.getShapeType();
		if (iShapeType == IVectorLayer.SHAPE_TYPE_POLYGON){
			s = createPolygonStyle();
		}
		else if (iShapeType == IVectorLayer.SHAPE_TYPE_LINE){
			s = createLineStyle();
		}
		else{
			s = createPointStyle();
		}

		WorldWindow window = ((WWGUIFactory)Meigas.getGUIFactory()).getWorldWindow();

		try {
			SimpleFeatureLayer sfLayer = new SimpleFeatureLayer(m_sName,
						getFeatureSource(),
						s, window, 1024);
			this.create(sfLayer);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public void saveShapefile() {

		if (getFeatureSource() != null) {
			FeatureStore<SimpleFeatureType, SimpleFeature> featureStore = null;
			try {
				SimpleFeatureType ft = getFeatureSource().getSchema();

				File file = new File(m_sFilename);
				Map<String, Serializable> params = new HashMap<String, Serializable>();
				params.put(ShapefileDataStoreFactory.URLP.key, file.toURI()
						.toURL());
				params.put(ShapefileDataStoreFactory.CREATE_SPATIAL_INDEX.key,
						false);

				FileDataStoreFactorySpi factory = new ShapefileDataStoreFactory();
				ShapefileDataStore dataStore = (ShapefileDataStore) factory
						.createNewDataStore(params);
				dataStore.createSchema(ft);
				featureStore = (FeatureStore<SimpleFeatureType, SimpleFeature>) dataStore.getFeatureSource();
			} catch (IOException e) {
				e.printStackTrace();
				return;
			}

			try {
				// no transaction for shapefile so it doesn't copy all features
				// into memory for the transaction. Just write
				// directly to disk.
				FeatureCollection<SimpleFeatureType, SimpleFeature> collection = getFeatureSource().getFeatures();
				featureStore.addFeatures(collection);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	public Rectangle2D getFullExtent() {

		if (m_BaseDataObject != null) {
			try {
				ReferencedEnvelope bounds = getFeatureSource().getFeatures().getBounds();
				return new Rectangle2D.Double(bounds.getMinX(), bounds
						.getMinY(), bounds.getWidth(), bounds.getHeight());
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		} else {
			return null;
		}

	}

	public String getFilename() {

		return m_sFilename;

	}

	public Object getCRS() {

		return m_CRS;

	}

	public void setName(String name) {

		m_sName = name;

	}

	@SuppressWarnings("unchecked")
	private FeatureSource<SimpleFeatureType, SimpleFeature> getFeatureSource() {
		if (m_BaseDataObject instanceof SimpleFeatureLayer){
			return (FeatureSource<SimpleFeatureType, SimpleFeature>) ((SimpleFeatureLayer)m_BaseDataObject).getFeatureSource();
		}
		else{
			return (FeatureSource<SimpleFeatureType, SimpleFeature>) m_BaseDataObject;
		}
	}

}
